home *** CD-ROM | disk | FTP | other *** search
/ IRIX 6.5 Applications 2001 May / SGI IRIX 6.5 Applications 2001 May.iso / dev / insight_dev.idb / usr / share / Insight / bin / indexgen_sgidocbk.z / indexgen_sgidocbk
Encoding:
Text File  |  2001-04-05  |  22.6 KB  |  804 lines

  1. #!/usr/bin/perl5
  2.  
  3. ####################################################################
  4. #
  5. #  Name: indexgen_sgidocbk
  6. #
  7. #  Note: PERL 5.004 or greater is required for this script.
  8. #  
  9. #  Function: scan an SGML file to find all instances of <indexterm>
  10. #    tags and use the information contained in those tags to
  11. #    create a separate file that contains an SGML index for the
  12. #    SGIDOCBK DTD that begins with the <index> tag and ends with
  13. #    the </index> tag.
  14. #
  15. #  Author: Adrian Daley
  16. #
  17. #  Other Information:
  18. #    When STDIN is used for input, <indexterm> tags without id attributes
  19. #    are ignored and not used in the index.  When a file is used for input
  20. #    the id attribute will be fixed on all <indexterm> tags that are
  21. #    lacking them if the '-q' command line argument is not used.
  22. #
  23. #  Version 0.5 - 9/28/98
  24. #    Initial version with most functionality and not much support for
  25. #    "see" and "seealso" parts of index terms.
  26. #
  27. #  Version 1.0 - 10/8/98
  28. #    Added support for "see" and "seealso" tags in <indexterm> and
  29. #    <indexentry> tags.  Refer to the ProcessTerm, CombineIdenticalTerms,
  30. #    and PrintIndex functions for more information on how they are
  31. #    handled.
  32. #
  33. #    Changed the <index> structure to use <indexdiv> structures with
  34. #    <title> tags to separate parts of the index that start with different
  35. #    letters.
  36. #
  37. #    Added -q command line option to bypass ID attribute checks
  38. #
  39. #    Added additional comments and documentation
  40. #
  41. #  Version 1.1 - 10/12/98
  42. #    Fixed bug related to <comment></comment> handling
  43. #    
  44. #    Now removes the following characters from sort as values to
  45. #    insure the proper sort order: '$', '/', '.', '<', '"', '-'
  46. #
  47. #  Version 1.2 - 3/1/99
  48. #    Changed CleanInput function to move any <indexterm> tags that
  49. #    occur within a <title> or <tbltitle> to just before the title.
  50. #    This is needed for the Inso stylesheet content() PVF.
  51. #
  52. #    Removed the use of <indexdiv> tags in the output to
  53. #    reduce the dependencies on specific languages
  54. #
  55. #    Added some hints as to how to localize the program
  56. #
  57. #  Version 1.3 - 02Mar99        (Ferg / gferg@sgi.com)
  58. #    Localization work; locale map and setlocale() implemented
  59. #
  60. ####################################################################
  61.  
  62. # localization; see perllocale(1) for details
  63. #
  64. # perl 5.004 can be installed from freeware.sgi.com or internally
  65. # at : hoshi.engr:/usr/local/dist/perl5/5.004/
  66. #
  67. require 5.004;
  68.  
  69. # use the locale for the life of the program
  70. #
  71. use locale;
  72. use POSIX qw(locale_h);
  73. use POSIX qw(strcoll);
  74.  
  75.  
  76. my($input_file, $output_file);
  77. local($verbose) = 1;
  78. my(@terms, @sorted_terms);
  79. my($total, $unique_entries);
  80. my($lang) = '';
  81. my($locale) = '';
  82.  
  83. # Mapping from SGIDOCBK LANG attrib to supported IRIX system locales
  84. #
  85. # Note : We may want to expand the left-hand side to include additional
  86. #        variations
  87. #
  88. # See  : http://localize.engr/root/products/IRIXLocales/locales_6_5.html
  89. #
  90. my(%locales) = (
  91.         'C'        =>    'C',        # English
  92.         'en'        =>    'C',        # English
  93.         'de'        =>    'de',        # German
  94.         'fr'        =>    'fr',        # French
  95.         'es'        =>    'es',        # Spanish
  96.         'jp'        =>    'ja_JP.EUC',    # Japanese
  97.         'ja_JP'        =>    'ja_JP.EUC',    # Japanese
  98.         'ja_JP.EUC'    =>    'ja_JP.EUC',    # Japanese
  99.         'ja_JP.ujis'    =>    'ja_JP.EUC',    # Japanese
  100.         'ja_JP.eucJP'    =>    'ja_JP.EUC',    # Japanese
  101.         'ja_JP.SJIS'    =>    'ja_JP.SJIS',    # Japanese (shift-JIS)
  102.         'ja.SJIS'    =>    'ja_JP.SJIS',    # Japanese (shift-JIS)
  103.         'zh_TW'        =>    'zh_TW.ucns',    # Traditional Chinese
  104.         'zh_TW.ucns'    =>    'zh_TW.ucns',    # Traditional Chinese
  105.         'zh_TW.EUC'    =>    'zh_TW.ucns',    # Traditional Chinese
  106.         'zh_TW.big5'    =>    'zh_TW.big5',    # Traditional Chinese
  107.         'zh_CN'        =>    'zh_CN.ugb',    # Simplified Chinese
  108.         'zh_CN.ugb'    =>    'zh_CN.ugb',    # Simplified Chinese
  109.         'zh_CN.EUC'    =>    'zh_CN.ugb',    # Simplified Chinese
  110.         'zh_CN.gbk'    =>    'zh_CN.gbk',    # Simplified Chinese
  111.         'zh_CN.eucgbk'    =>    'zh_CN.gbk',    # Simplified Chinese
  112.         'ko'        =>    'ko_KR.euc',    # Korean
  113.         'ko_KR'        =>    'ko_KR.euc',    # Korean
  114.         'ko_KR.euc'    =>    'ko_KR.euc',    # Korean
  115.         'ko_KR.eucKR'    =>    'ko_KR.euc'    # Korean
  116.         );
  117.  
  118. # Global delimiter value for strings
  119. # This value should be see such that it never will occur in a string
  120. $delimiter = ':%:%:';
  121.  
  122. ($input_file, $output_file, $verbose) = ProcessArgs();
  123.  
  124. # Read the input file into a single string replacing newlines with spaces
  125. if($verbose) { print STDERR "\tReading file...\n"; }
  126. $buffer = '';
  127. while($line = <$input_file>) {
  128.     chomp $line;
  129.     $buffer .= $line.' ';
  130. }
  131.  
  132. #remove all <comment></comment> sections in case they contain <indexterm> tags
  133. $buffer =~ s#<comment[^>]*>.*?</comment>##img;
  134.  
  135. # find the locale for this document from the <sgidocbk> LANG attribute.
  136. #
  137. if($buffer =~ /<sgidocbk[^>]*LANG\s*=\s*"([^"]+)"/im) {
  138.     $lang = $1;
  139. } else {
  140.     $lang = "C";
  141. }
  142.  
  143.  
  144. # cannot find the correct locale for the specified book LANG
  145. #
  146. if(($locale = $locales{$lang}) eq '') {
  147.     print STDERR "\tWARNING: Locale for LANG attribute '$lang' was ",
  148.              "not found; 'C' will be used.\n";
  149.     $locale = 'C';
  150. }
  151.  
  152. if($verbose) { print STDERR "\tUsing LANG='$lang' with locale='$locale'\n"; } 
  153.  
  154. # set the proper locale
  155. #
  156. # LC_CTYPE   needed for uc(), lc(), ucfirst(), lcfirst()
  157. # LC_COLLATE needed for lt, le, cmp, ge, gt, strcoll(), sort()
  158. #
  159. if( !setlocale(LC_CTYPE, $locale) ) {
  160.     print STDERR "\tWARNING: setlocale() for locale '$locale' failed; ",
  161.              "'C' will be used.\n";
  162.     setlocale(LC_CTYPE, 'C');
  163.     setlocale(LC_COLLATE, 'C');
  164. } else {
  165.     setlocale(LC_COLLATE, $locale);
  166. }
  167.  
  168.  
  169. # iteratively find all of the <indexterm>s and create an index entry for them
  170. if($verbose) { print STDERR "\tParsing <indexterms>...\n"; }
  171. while($buffer =~ m#<indexterm([^>]*)>(.+?)</indexterm>#ios) {
  172.         $result = ProcessTerm($1, $2);
  173.         if(defined($result)) {
  174.                 push(@terms, $result);
  175.         }
  176.         # only need to look at the remainder of the buffer now
  177.         $buffer = $';
  178. }
  179.  
  180. $total = $#terms + 1;
  181.  
  182. if($total == 0) {
  183.     print STDERR "\tNo index terms found.  No index will be created.\n";
  184.     exit(0);
  185. }
  186.  
  187. if($verbose) { print STDERR "\tSorting $total terms...\n"; }
  188. @sorted_terms = sort by_alpha @terms;
  189.  
  190. if($verbose) { print STDERR "\tCombining duplicate terms...\n"; }
  191. $unique_entries = CombineIdenticalTerms(\@sorted_terms);
  192.  
  193. if($verbose) { print STDERR "\tPrinting $unique_entries unique terms...\n"; }
  194. PrintIndex(\@sorted_terms, $output_file);
  195.  
  196. print STDERR "\tFinished: $total terms found and indexed in $unique_entries unique entries.\n";
  197.  
  198. ### END MAIN PROGRAM ###
  199.  
  200. ###############################################################################
  201. #
  202. #    Read in the command line arguments.  Open the input and output
  203. #    file handles and return references to them.  If the input source
  204. #    is a file, then call CleanInput to check and fix the ID attribute
  205. #    on the file.  If the input is STDIN and/or the output is STDOUT,
  206. #    references to the respective filehandle will be returned.  The "-s"
  207. #    argument determines if verbose status reports are not generated.
  208. #    If the -q argument is used, checking and fixing attribute tags in the
  209. #    input file will be bypassed for faster processing if the user knows
  210. #    it is not needed.
  211. #
  212. #    Returns: (filehandle input, filehandle output, boolean verbose_status)
  213. #
  214. ###############################################################################
  215. sub ProcessArgs() {
  216.     local($input) = "";
  217.     local($output) = "";
  218.     local($verbose) = 1;
  219.     local($do_cleanup) = 1;
  220.  
  221.     while($arg = shift(@ARGV)) {
  222.         if($arg =~ /^-h/) { Usage(); }
  223.         elsif($arg =~ /^-q/) { $do_cleanup = 0; }
  224.         elsif($arg =~ /^-s/) { $verbose = 0; }
  225.         elsif($arg =~ /^-i/) { 
  226.             $input = shift(@ARGV); 
  227.             if($input eq "") {
  228.                 Usage();
  229.             } elsif(! -e $input) {
  230.                 print "\nInput file doesn't exist!\n";
  231.                 exit(1);
  232.             }
  233.         }
  234.         elsif($arg =~ /^-o/) { 
  235.             $output = shift(@ARGV); 
  236.             if($output eq "") {
  237.                 print "\nInput file name not specified correctly";
  238.                 Usage();
  239.             }
  240.         } else {
  241.             print STDERR "\nThe argument '$arg' is not supported.";
  242.             Usage();
  243.         }
  244.     }
  245.  
  246.     # open the input and output files
  247.     if($input ne '') {
  248.         if($do_cleanup) {
  249.             # first clean up the file to make sure every <indexterm> as an id attribute
  250.             if($verbose) { print STDERR "\tCorrecting <indexterm> id attributes...\n"; }
  251.             CleanInput($input);
  252.         }
  253.         open(INPUT, "$input") || die "Unable to open input file: $input\n";
  254.         $input = \*INPUT;
  255.     } else {
  256.         if($verbose) { print STDERR "\tReading from STDIN\n"; }
  257.         $input = \*STDIN;
  258.     }
  259.  
  260.     if($output ne '') {
  261.         open(OUTPUT, ">$output") || die "Unable to open output file: $output\n";
  262.         $output = \*OUTPUT;
  263.     } else {
  264.         if($verbose) { print STDERR "\tWriting to STDOUT\n"; }
  265.         $output = \*STDOUT;
  266.     }
  267.  
  268.     return($input, $output, $verbose);
  269. }
  270.  
  271. #################################################################################
  272. #
  273. #    Parses an <indexterm> content to gather the <primary>,<secondary>,<tertiary>
  274. #    terms, primary, secondary, tertiary sort as attribute values, a
  275. #    single <see> tag content and the content of multiple <seealso> tags.
  276. #
  277. #    After parsing the input for the above values, all of the values are cleaned
  278. #    up to remove extra spaces.  For terms with no sort as values, the respective
  279. #    term is used with all SGML tags removed.
  280. #
  281. #    Input:
  282. #        $term - string of the indexterm tag (ex. '<indexterm ID="???">')
  283. #        $content - the entire string between the <indexterm> and </indexterm>
  284. #
  285. #    Return Value: a reference to an array with the following information.  If
  286. #        a valid indexterm is not found, the value undef is returned.
  287. #
  288. #        0 - the indexterm's ID attribute value - if a <see> value was
  289. #            found, this value will be ''.    
  290. #        1 - <primary> tag content
  291. #        2 - <secondary> tag content
  292. #        3 - <tertiary> tag content
  293. #        4 - primary sort as value
  294. #        5 - secondary sort as value
  295. #        6 - tertiary sort as value
  296. #        7 - <see> tag content
  297. #        8 - <seealso> tag(s) content.  
  298. #            Multiple values are joined with '$delimiter'
  299. #
  300. #################################################################################
  301. sub ProcessTerm {
  302.     local($term, $content) = @_;
  303.     local($id, $primary, $secondary, $tertiary, $p_sort, $s_sort, $t_sort, $temp, $see, $seealso);
  304.  
  305.     if($term =~ m#id="([^"]+)#i) {
  306.         $id = $1;
  307.     } else {
  308.         print STDERR "Warning: ID attribute not found in $content. Skipping...\n";
  309.         return(undef);
  310.     }
  311.  
  312.     if($content =~ m#<primary([^>]*)>(.+?)</primary>#i) {
  313.         $primary = $2;
  314.         $temp = $1;
  315.         if($temp =~ m#sortas="([^"]+)#i) {
  316.             $p_sort = $1;
  317.         }
  318.  
  319.         if($content =~ m#<secondary([^>]*)>(.+?)</secondary>#i) {
  320.             $secondary = $2;
  321.             $temp = $1;
  322.             if($temp =~ m#sortas="([^"]+)#i) {
  323.                 $s_sort = $1;
  324.             }
  325.                 
  326.             if($content =~ m#<tertiary([^>]*)>(.+?)</tertiary>#i) {
  327.                 $tertiary = $2;
  328.                 $temp = $1;
  329.                 if($temp =~ m#sortas="([^"]+)#i) {
  330.                     $t_sort = $1;
  331.                 }
  332.             } 
  333.         }
  334.  
  335.         # search for <see> and <seealso> tags
  336.         if($content =~ m#<see[^>]*>(.+?)</see>#i) {
  337.             $see = $1;
  338.         }
  339.  
  340.         $seealso = '';
  341.         while($content =~ s#<seealso[^>]*>(.+?)</seealso>##i) {
  342.             if($seealso eq '') {
  343.                 $seealso = $1;
  344.             } else {
  345.                 $seealso .= "$delimiter$1";
  346.             }
  347.         }
  348.     } else {
  349.         print STDERR "Warning: Invalid primary indexterm in $id. Skipping...\n";
  350.         $primary = '';
  351.     }
  352.  
  353.     if($primary ne '' && $id ne '') {
  354.         # set, clean-up, and modify the ?_sort variables
  355.         if($p_sort eq '') {
  356.             $p_sort = $primary;
  357.         }
  358.         if($s_sort eq '') {
  359.             $s_sort = $secondary;
  360.         }
  361.         if($t_sort eq '') {
  362.             $t_sort = $tertiary;
  363.         }
  364.  
  365.         @new_term = ($id, $primary, $secondary, $tertiary, $p_sort, $s_sort, $t_sort, $see, $seealso);
  366.  
  367.         # remove extra whitespace before and after the entries
  368.         for $i (1..8) {
  369.             $new_term[$i] =~ s/^\s+|\s+$//g;
  370.         }
  371.  
  372.         for $i (4..6) {
  373.             # remove extraneous tags from the sortas terms
  374.             $new_term[$i] =~ s/<[^>]*>/ /g;
  375.             $new_term[$i] =~ s/\s+/ /g;
  376.  
  377.             # remove extra characters that may affect sorting
  378.              $new_term[$i] =~ s/[\/\.\$<"-]//g;
  379.  
  380.             # Double check to remove extra whitespace
  381.             $new_term[$i] =~ s/^\s+|\s+$//g;
  382.  
  383.             # convert all sortas terms to lower case.
  384.             $new_term[$i] = lc($new_term[$i]);
  385.         }
  386.  
  387.         # remove the ID attribute field for terms with a "see" value
  388.         if($new_term[7] ne '') {
  389.             $new_term[0] = '';
  390.         }
  391.  
  392.         if($new_term[4] eq '') {
  393.             # skip terms that only have SGML tags as their content
  394.             return(undef);
  395.         } else {
  396.             # return a reference to the array of indexterm information
  397.             return([@new_term]);
  398.         }
  399.     } else {
  400.         return(undef);
  401.     }
  402. }
  403.  
  404. ####################################################################
  405. #
  406. #    Sorting routine to determine the ordering of an array of
  407. #    index terms.  $a and $b sort items are always references to
  408. #    the array described in the ProcessTerm function.  All terms
  409. #    are sorted using only their sort as value.
  410. #
  411. ####################################################################
  412. sub by_alpha {
  413.     local($result, $count);
  414.  
  415.     # loop through the primary, secondary, and tertiary sort as values.
  416.  
  417.     # the function returns as soon as a result is found because the two terms
  418.     # are not identical at some level.  If they are identical, the terms are
  419.     # sorted by numeric reference values to make the sort well defined.
  420.  
  421.     $result = 0;
  422.     $count = 1;
  423.     while($result == 0 && $count <= 3) {
  424.  
  425.         $result = strcoll($a->[$count+3], $b->[$count+3]);
  426.         $count += 1;
  427.     }
  428.  
  429.     if($result != 0) {
  430.         return($result);
  431.     } else {
  432.         # last ditch sorting on otherwise equivalent terms to make
  433.         # sure the sort order is well defined.
  434.         return (strcoll($a, $b));
  435.     }
  436. }
  437.  
  438. ####################################################################
  439. #
  440. #    CombineIdenticalTerms looks for duplicate index entries
  441. #    (the same index term at more than one location in the book)
  442. #    and combines it into a single entry that will appear in the
  443. #    index with multiple references.  This function assumes the
  444. #    array of terms has already been sorted so that identical terms
  445. #    occur consecutively.  
  446. #
  447. #    Two terms are considered identical if their primary, secondary,
  448. #    and tertiary sortas terms are identical.  If the sortas term
  449. #    is not explictly specified, it is the term with all SGML markup
  450. #    removed.
  451. #
  452. #    Terms are combined by joining their ID attribute values with
  453. #    $delimiter in the 0 array location.  Then the "see" and "see also"
  454. #    attributes are combined.  Finally, the extra occurance of the
  455. #    term is removed from the array by setting its value to undef.
  456. #
  457. #    Note: Combined terms with different SGML markup in the index term
  458. #    will use the SGML tags for the first occurence of the tag.
  459. #
  460. ####################################################################
  461. sub CombineIdenticalTerms {
  462.     local($terms) = @_;
  463.     local($count);
  464.     local($term, $prev_term);
  465.  
  466.     $count = $#terms + 1;
  467.  
  468.     $prev_term = $terms->[0];
  469.  
  470.     for $i (1 .. $#terms) {
  471.         $term = $terms->[$i];
  472.  
  473.         if($prev_term->[4] eq $term->[4] && 
  474.            $prev_term->[5] eq $term->[5] && 
  475.            $prev_term->[6] eq $term->[6])
  476.         {
  477.             # combine "see" values
  478.             if($term->[7] ne '') {
  479.                 if($prev_term->[7] ne '') {
  480.                     $prev_term->[7] .= "$delimiter$term->[7]";
  481.                 } else {
  482.                     $prev_term->[7] = $term->[7];
  483.                 }
  484.             }
  485.  
  486.             # combine the "see also" values
  487.             if($term->[8] ne '') {
  488.                 if($prev_term->[8] ne '') {
  489.                     $prev_term->[8] .= "$delimiter$term->[8]";
  490.                 } else {
  491.                     $prev_term->[8] = $term->[8];
  492.                 }
  493.             }
  494.             
  495.             # combine the ID attribute values
  496.             if($term->[0] ne '') {
  497.                 if($prev_term->[0] ne '') {
  498.                     $prev_term->[0] .= "$delimiter$term->[0]";
  499.                 } else {
  500.                     $prev_term->[0] = $term->[0];
  501.                 }
  502.             }
  503.  
  504.             # remove the duplicate term
  505.             $terms->[$i] = undef;
  506.  
  507.             #decrease the count of unique entries
  508.             --$count;
  509.         } else {
  510.             $prev_term = $term;
  511.         }
  512.     }
  513.     return($count);
  514. }
  515.  
  516. ##########################################################################
  517. #
  518. #    Prints the SGIDOCBK SGML index.  The output file contains a valid
  519. #    index starting with an <index> tag and closing with a </index> tag
  520. #
  521. #    The overall format of the index is:
  522. #    <INDEX>
  523. #        <INDEXENTRY></INDEXENTRY> (one entry for each term)
  524. #    </INDEX>
  525. #
  526. #    where index entries are formatted as:
  527. #    <INDEXENTRY>
  528. #    <PRIMARYIE></PRIMARYIE> - 1 and only 1
  529. #        <SEEIE></SEEIE> - 0 or more
  530. #        <SEEALSOIE></SEEALSOIE> - 0 or more
  531. #        <SECONDARYIE></SECONDARYIE> - 0 or more
  532. #        <SEEIE></SEEIE> - 0 or more
  533. #        <SEEALSOIE></SEEALSOIE> - 0 or more
  534. #        <TERTIARYIE></TERTIARYIE> - 0 or more after each <SECONDARYIE>
  535. #            <SEEIE></SEEIE> - 0 or more
  536. #            <SEEALSOIE></SEEALSOIE> - 0 or more
  537. #    </INDEXENTRY>
  538. #
  539. #    Note: <XREF> tags are inserted as needed in the <PRIMARYIE>,
  540. #        <SECONDARYIE>, and <TERTIARYIE> tags to create links to
  541. #        the appropriate location in the book.
  542. #
  543. ##########################################################################
  544. sub PrintIndex {
  545.     local($terms, $output) = @_;
  546.     local($term, $count, $term_level, $prev_term);
  547.     local(@current_open_term);
  548.  
  549.     @levels = ('IGNORED', 'PRIMARYIE', 'SECONDARYIE', 'TERTIARYIE');
  550.  
  551.     print $output "<INDEX>\n\n";
  552.  
  553.     for $i (0 .. $#terms) {
  554.         $term = $terms->[$i];
  555.  
  556.         # skip invalid, undefined entries
  557.         if(! defined($term)) { next; }
  558.  
  559.         # determine which term level should get the <xrefs> added
  560.         $count = 0;
  561.         for $j (1..3) {
  562.             if($term->[$j] ne '') { 
  563.                 ++$count; 
  564.             }
  565.         }
  566.  
  567.         $term_level = 1;
  568.  
  569.         while($term->[$term_level] ne '' && ($term_level >= 1 && $term_level <= 3)) {
  570.             if($current_open_term[$term_level] eq $term->[$term_level+3]) {
  571.                 ++$term_level;
  572.                 next;
  573.             } else {
  574.                 if($term_level == 1) {
  575.                     # trick to not open an <indexentry> tag
  576.                     # at the beginning of the document
  577.                     if($i > 0) {
  578.                         print $output "</INDEXENTRY>\n\n";
  579.                         # reset open term array
  580.                         @current_open_term = ();        
  581.                     }
  582.  
  583.                     print $output "<INDEXENTRY>\n";
  584.                 }
  585.             }
  586.  
  587.             # indent tags to improve readiblity
  588.             for $k (1..($term_level*3)) { print $output " "; }
  589.  
  590.             print $output "<$levels[$term_level]>$term->[$term_level]";
  591.             $current_open_term[$term_level] = $term->[$term_level+3];
  592.  
  593.             # if we're at the proper level add the <xrefs>
  594.             # $term->[0], the ID value may have multiple entries
  595.             # separated by "$delimiter"
  596.             if($count == $term_level && $term->[0] ne '') {
  597.                 @refs = split(/$delimiter/, $term->[0]);
  598.                 foreach $ref (@refs) {
  599.                     next if($ref eq '');
  600.                     print $output " <XREF LINKEND=\"$ref\">";
  601.                 }
  602.             }
  603.             print $output "</$levels[$term_level]>\n";
  604.  
  605.             # print out any <SEEIE> and <SEEALSOIE> tags
  606.             if($count == $term_level) {
  607.                 if($term->[7] ne '') {
  608.                     $term->[7] = RemoveDuplicates($term->[7]);
  609.                     @refs = split(/$delimiter/, $term->[7]);
  610.                     foreach $ref (@refs) {
  611.                         next if($ref eq '');
  612.                         for $k (1..(($term_level+1)*3)) { print $output " "; }
  613.                         print $output "<SEEIE>$ref</SEEIE>\n";
  614.                     }
  615.                 }
  616.  
  617.                 if($term->[8] ne '') {
  618.                     $term->[8] = RemoveDuplicates($term->[8]);
  619.                     @refs = split(/$delimiter/, $term->[8]);
  620.                     foreach $ref (@refs) {
  621.                         next if($ref eq '');
  622.                         for $k (1..(($term_level+1)*3)) { print $output " "; }
  623.                         print $output "<SEEALSOIE>$ref</SEEALSOIE>\n";
  624.                     }
  625.                 }
  626.             }
  627.             ++$term_level;
  628.         }
  629.     }
  630.  
  631.     # close the last <indexentry> tag if any existed
  632.     if($#terms != -1) {
  633.         print $output "</INDEXENTRY>\n\n";
  634.     }
  635.     print $output "</INDEX>\n";
  636. }
  637.  
  638. ########################################################################
  639. #
  640. #    Given a string of multiple values separated by "$delimiter" remove any
  641. #    duplicate values and return a string of unique values separated by
  642. #    "$delimiter" and sorted in case-insensitive, ASCII order.  Any SGML tags
  643. #    that may exist in the values are not used to determine identical
  644. #    values.
  645. #
  646. ########################################################################
  647. sub RemoveDuplicates {
  648.     local($string) = @_;
  649.     local(%hash);
  650.  
  651.     @parts = split(/$delimiter/, $string);
  652.     foreach $part (@parts) {
  653.         $key = $part;
  654.  
  655.         # remove SGML tags and extra spaces from the key values
  656.         # used to determine if two values are identical.
  657.         $key =~ s/<[^>]*>/ /g;
  658.         $key =~ s/\s+/ /g;
  659.         $key =~ s/^\s+|\s+$//g;
  660.  
  661.         $hash{$key} = $part;
  662.     }
  663.  
  664.     # LOCALIZE: the returned values should be sorted according to the best locale
  665.     return(join("$delimiter", sort { lc($a) cmp lc($b) } (values(%hash))));
  666. }
  667.  
  668. #########################################################################
  669. #
  670. #    Given a file name, the <indexterm> tags in the file will be
  671. #    checked for valid ID attributes.  Tags will invalid or missing
  672. #    attribute values will have new, unique values provided.
  673. #
  674. #    Note: to differentiate generated attributes from existing ones
  675. #    all generated values start with "IG"
  676. #
  677. #    Input: file name of the file that needs to be verified.
  678. #
  679. #    Returns: nothing
  680. #
  681. #########################################################################
  682. sub CleanInput {
  683.     local($file) = @_;
  684.     local($id_count) = 0;
  685.     local($buffer) = "";
  686.     
  687.     open(INPUT, "$file") || die "Can't open input file $input for clean up.\n";
  688.         while($line = <INPUT>) {
  689.                 $buffer .= $line;
  690.         }
  691.         close(INPUT);
  692.  
  693.     $newbuffer = '';
  694.     while($buffer =~ m#<(title|tbltitle)[^>]*>.*?<\/\1>#ims) {
  695.         $newbuffer .= $`;
  696.         $title = $&;
  697.         $buffer = $';
  698.         $terms = '';
  699.         while($title =~ s#(<indexterm[^>]*>.*?<\/indexterm>)##ims) {
  700.             $terms .= $1;
  701.             # make sure the changes are saved
  702.             $id_count = 1;
  703.         }
  704.         # remove extra newlines left over from the <indexterm> tags
  705.         $title =~ s#^\n+|\n+$##imsg;
  706.         $newbuffer .= "$terms\n$title";
  707.     }
  708.     $newbuffer .= $buffer;
  709.     $buffer = undef;
  710.  
  711.     @chunks = split(/(<\/indexterm[^>]*>)/im, $newbuffer);
  712.     $newbuffer = undef;
  713.     for $i (0 .. $#chunks) {
  714.         $line = $chunks[$i];
  715.         if($line =~ /<indexterm([^>]*)>/im) {
  716.             $temp = $1;
  717.             if($temp =~ /id="([^"]*)"/im) {
  718.                 $id = $1;
  719.                 if($id =~ /[A-Za-z].*/) {
  720.                     # good id value, no changes needed
  721.                 } else {
  722.                     # correct bad ID value
  723.                     $id = "IG".$$.$id_count;
  724.                     ++$id_count;
  725.                     $line =~ s/(<indexterm.*id=")[^"]*/$1$id/im;
  726.                 }
  727.             } else {
  728.                 # no id attribute found - create a new one
  729.                 $id = "IG".$$.$id_count;
  730.                 ++$id_count;
  731.                 $line =~ s/(<indexterm[^>]*)/$1 ID="$id"/i;
  732.             }
  733.         }
  734.         $chunks[$i] = $line;
  735.     }
  736.  
  737.     if($id_count > 0) {
  738.         # need to save changes
  739.         open(OUTPUT, ">$file") || die "Can't open output file: $file for clean up\n";
  740.         print OUTPUT join("", @chunks);
  741.         close(OUTPUT);
  742.     }
  743. }
  744.  
  745. ####################################################################
  746. #
  747. #    Prints the program's usage statement and exits.
  748. #
  749. ####################################################################
  750. sub Usage {
  751.  
  752. $name = $0;
  753. $name =~ s#.*/##g;
  754.  
  755. print <<END_USAGE;
  756.  
  757. $name Version 1.3
  758.  
  759. Usage: $name [-h] [-s] [-o <FileName>] [-i <FileName>]
  760.  
  761.   -h         Print this help message
  762.   -i <filename> Read input from specified file rather than STDIN
  763.   -o <filename>    Write output to specified file rather than STDOUT
  764.   -s        Silent mode; don't print update messages to STDERR
  765.   -q        Quick; don't attempt to check/fix <indexterm> ID attributes
  766.             Invalid <indexterm> tags will be skipped.
  767.  
  768. END_USAGE
  769.  
  770.     exit(1);
  771.  
  772. }
  773.  
  774. ####################################################################
  775. #
  776. #    Prints the contents of an index term entry.
  777. #    This should only be used for debugging.
  778. #
  779. #    Input: a reference to the entry array location
  780. #
  781. ####################################################################
  782. sub DebugEntry {
  783.  
  784.     print "\n----------------------------------------------\n";
  785.  
  786.     local($ref) = @_;
  787.     if(! defined($ref)) {
  788.         print "DebugEntry - undefined reference\n";
  789.         return;
  790.     }
  791.  
  792.     # LOCALIZE
  793.     # may need to dereference collated array entries
  794.     foreach $i (0..8) {
  795.         print "$i - $ref->[$i]\n";
  796.     }
  797.  
  798.     print "-------------------------------------------------\n";
  799. }
  800.  
  801. ####################################################################
  802.  
  803.  
  804.